using System;
using System.Collections;
using System.Diagnostics;
using System.Html;
using System.Runtime.CompilerServices;
using Minions.Classes;
using Nsb.Classes;
using jQueryApi;

namespace MinionsEdit.Classes
{
    [IgnoreNamespace]
    public sealed class MiMetrics
    {
        public const double MinWidth = MinionEdit.MinSize;
        public const double MinHeight = MinionEdit.MinSize/2;

        public double Dx;
        public double Dy;
        public jQueryObject Handle;
        public double[] IPos;
        public Dimension StDim = new Dimension();
        public Pos StOff;
        private double _bdrX;
        private double _bdrY;
        private double _ratioX;
        private double _ratioY;
        //public Pos StPos;

        private jQueryObject _element;
        private Pos _enPos;
        private string _fromId;
        private MinionEdit _mm;
        private double _parentHt;
        private double _parentWd;
        private bool _working;

        public Pos EnPos
        {
            get { return Pos.Clone(_enPos); }
        }

        private MinionEdit TheMinion
        {
            get { return MinionEdit.GetMinionById(_fromId); }
        }

        [AlternateSignature]
        private extern void Initialize(jQueryEvent e);

        private void Initialize(jQueryEvent e, jQueryUiObject ui)
        {
            if (_working)
            {
                throw new Exception("Double use of MiMetrics");
            }
            _working = true;
            _element = jQuery.FromElement(e.Target).Closest(Minion.DotMinionClassName);
            _mm = MinionEdit.GetMinionFromEl(_element);
            MiUiOp.Init(_mm, e, ui);
            _fromId = _mm.Id;
            jQueryObject parent = _element.OffsetParent();
            // ReSharper disable PossibleNullReferenceException
            Debug.Assert(parent != null, "offset parent != null");
            _parentWd = parent.GetWidth();
            _parentHt = parent.GetHeight();
            // ReSharper restore PossibleNullReferenceException
        }

        private void Finish()
        {
            _working = false;
        }

        private void ReElement()
        {
            // In case a drop or something replaced the Minion get it again by id.
            _mm = TheMinion;
            _element = _mm.Element;
        }

        private double Cwd(double n)
        {
            return (_mm.Data.relative ? (n/_parentWd*100) : n);
        }

        private string Ch(double n)
        {
            return (_mm.Data.relative ? (n/_parentHt*100) : n) + _mm.Units;
        }

        public MiMetrics SnapDragStart(jQueryEvent e, jQueryUiObject ui)
        {
            Initialize(e, ui);
            _element.Plugin<Jewl>().Droppable(new Dictionary("disabled", true));
            MiUiOp.SetCancel(delegate { TheMinion.Element.Trigger("mouseup"); });
            //StPos = (Pos) (object) _element.Position();
            StOff = (Pos) (object) _element.GetOffset();
            StDim.width = _element.GetOuterWidth();
            StDim.height = _element.GetOuterHeight();

            double tilt = Math.Abs((double) Type.GetField(_element[0], "tilt"));
            if (tilt > 0)
            {
                double ww = StDim.width;
                double hh = StDim.height;
                double tt = _element.Plugin<SnapTo>().SnapToTilt(tilt);
                StDim.width = _element.Plugin<SnapTo>().SnapToTiltW(tt, ww, hh);
                StDim.height = _element.Plugin<SnapTo>().SnapToTiltH(tt, ww, hh);
            }

            _element.AddClass(MinionEdit.GigMe);
            _element.Find(Minion.DotMinionClassName).AddClass(MinionEdit.GigMe);
            _element.Plugin<SnapTo>().SnapCollect("div.Gig:not(" + MinionEdit.DotGigMe + ")");
            return this;
        }

        public void SnapDragDrag(jQueryEvent e)
        {
            jQueryPosition p = _element.GetOffset();
            if (e.AltKey)
                IPos = new double[] {p.Left, p.Top};
            else
                IPos = _element.Plugin<SnapTo>().SnapPos(p.Left, p.Top, StDim.width, StDim.height);
        }

        public void SnapDragStop(jQueryEvent e)
        {
            ReElement();
            _element.Show();
            if (MiUiOp.cancelled)
            {
                _element.Plugin<SnapTo>().SnapDump();
                _element.Plugin<Jewl>().Droppable(new Dictionary("disabled", false));
                MiUiOp.Finish();
                Finish();
                return;
            }
            jQueryPosition p = _element.GetOffset();
            //jQueryPosition p = _element.Position();
            if (!e.AltKey)
            {
                IPos = _element.Plugin<SnapTo>().SnapPos(p.Left, p.Top, StDim.width, StDim.height);
                //CvPos(IPos);
                _element.Offset((jQueryPosition) (object) new Dictionary("top", IPos[1], "left", IPos[0]));
                //_element.CSS((jQueryPosition)(object)new Dictionary("top", IPos[1] + _mm.Units, "left", IPos[0] + _mm.Units));
            }
            else
            {
                IPos = new double[] {p.Left, p.Top};
            }
            _element.Plugin<SnapTo>().SnapDump();
            Dx = IPos[0] - StOff.Left;
            Dy = IPos[1] - StOff.Top;
            //Inform.Debug("Drag to: dim.top={0}, dim.left={1}, off.top={2}, off.left={3}", _element.Position().Top, _element.Position().Left, IPos[1], IPos[0]);
            _enPos = CvPos((Pos) (object) _element.Position());
            _mm.Data.pos = EnPos;
            _mm.PaintMetrics(true); // Sanity feedback
            //Inform.Debug("New top={0}, left={1}", _element.Position().Top, _element.Position().Left);
            jQuery.Select(MinionEdit.DotGigMe).RemoveClass(MinionEdit.GigMe);
            _element.Plugin<Jewl>().Droppable(new Dictionary("disabled", false));
            MiUiOp.Finish();
            Finish();
        }

        private Pos CvPos(Pos p)
        {
            if (_mm.Data.relative)
            {
                double fh = _mm.Data.offsetParentWidth/_element.OffsetParent().GetWidth();
                double fv = _mm.Data.offsetParentHeight/_element.OffsetParent().GetHeight();
                p.Top *= fv;
                p.Left *= fh;
            }
            return p;
        }

        public MiMetrics SnapResizeStart(jQueryEvent e)
        {
            Initialize(e);
            Script.Literal("{0}.draggable('disable')", _element);
            MiUiOp.SetCancel(delegate { jQuery.Document.Trigger("mouseup"); });
            Evo.Trigger(MinionEdit.MaskSleepEv);
            Evo.Trigger(MinionEdit.MaskRubberbandEv, _mm);
            jQuery.Document.Bind("mousemove.MinionSize", SnapResizeMove);
            jQuery.Document.Bind("mouseup.MinionSize", SnapResizeStop);
            DomElement.CancelEvent(e);

            if (_element.Length > 0)
            {
                StDim.width = _element.GetOuterWidth();
                StDim.height = _element.GetOuterHeight();
                _ratioX = _mm.Data.dim.width / _element.GetWidth();
                _ratioY = _mm.Data.dim.height/_element.GetHeight();
                _bdrX = StDim.width - _element.GetWidth();
                _bdrY = StDim.height - _element.GetHeight();
                double tilt = Math.Abs((double) Type.GetField(_element[0], "tilt"));
                if (tilt > 0)
                {
                    double ww = StDim.width;
                    double hh = StDim.height;
                    double tt = _element.Plugin<SnapTo>().SnapToTilt(tilt);
                    StDim.width = _element.Plugin<SnapTo>().SnapToTiltW(tt, ww, hh);
                    StDim.height = _element.Plugin<SnapTo>().SnapToTiltH(tt, ww, hh);
                }
                _element.AddClass(MinionEdit.GigMe);
                _element.Plugin<SnapTo>().SnapCollect("div.Gig:not(" + MinionEdit.DotGigMe + ")");
                _element.RemoveClass(MinionEdit.GigMe);
                IPos = new double[] {StDim.width, StDim.height};
            }
            return this;
        }

        // TODO:
        // Restore center ruler
        // Determine why tilted bottom points are sometimes a few pixels off
        // Determine why snapping at the far edge snaps out of the zone instead of into the zone
        // Experiment with sticky snapping where the box stays put snapped while the mouse moves until out of the zone
        //

        private void SnapResizeMove(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            if (_element.Length > 0)
            {
                jQueryPosition p = _element.GetOffset();
                double wd = Math.Max(e.PageX - p.Left - _bdrX, MinWidth);
                double ht = Math.Max(e.PageY - p.Top - _bdrY, MinHeight);
                double ow = wd + _bdrX;
                double oh = ht + _bdrY;
                //double wdd = Cwd(wd);
                //_element.Width(wdd + _mm.Units);
                //if (_mm.Data.hasHeight)
                //    _element.Height(Ch(ht));

                IPos = _element.Plugin<SnapTo>().SnapDim(_element.GetOffset().Left + ow, _element.GetOffset().Top + oh, Math.Round(ow/2), Math.Round(oh/2));
                MinionData md = _mm.Data;
                if (md.relative)
                {
                    wd *= _ratioX;
                    ht *= _ratioY;
                    //wd *= (StDim.width/md.offsetParentWidth);
                    //ht *= (StDim.height/md.offsetParentHeight);
                }
                md.dim.width = wd;
                //if (_mm.Data.hasHeight)
                md.dim.height = ht;
                _mm.PaintMetrics();
                Evo.Trigger(MinionEdit.MaskRubberbandEv, _mm);
                //if (md.relative)
                //{
                //    string fs = Number.ParseDouble((string)md.css["font-size"] ?? "7.5pt") / md.initialWidth * wdd * 1.333 + "em";
                //    _element.CSS(new Dictionary("font-size", fs));
                //}
                //else
                //{
                //    string fs = Number.ParseInt((string) md.css["font-size"] ?? "7.5pt")/md.initialWidth*wdd*.1333 + "em";
                //    _element.CSS(new Dictionary("font-size", fs));
                //}
            }
        }

        private void SnapResizeStop(jQueryEvent e)
        {
            ReElement();
            jQuery.Document.Unbind(".MinionSize");
            Script.Literal("{0}.draggable('enable')", _element);
            if (!MiUiOp.cancelled)
            {
                //int wd = _element.GetWidth();
                //int ht = _element.GetHeight();
                //if (_element.Length > 0)
                //{
                //    int xo = Number.ParseInt(_element.GetCSS("padding-left")) + Number.ParseInt(_element.GetCSS("padding-right"));
                //    int yo = Number.ParseInt(_element.GetCSS("padding-top")) + Number.ParseInt(_element.GetCSS("padding-bottom"));
                //    int bw = Number.ParseInt(_element.GetCSS("border-right-width"))*2;
                //    int bh = Number.ParseInt(_element.GetCSS("border-bottom-width"))*2;
                //    wd = Math.Max(IPos[0] - _element.GetOffset().Left - xo - bw, MinWidth);
                //    ht = Math.Max(IPos[1] - _element.GetOffset().Top - yo - bh, MinHeight);
                //    _element.Plugin<SnapTo>().SnapDump();
                //    _element.Width(wd);
                //    if (_mm.Data.hasHeight)
                //        _element.Height(ht);
                //}
                //MinionData md = _mm.Data;
                //double iw = md.dim.width;
                //md.dim = new Dimension();
                //md.dim.width = wd;
                //md.dim.height = ht;
                //md.dim = CvDim(md.dim);
                //_mm.PaintMetrics();
                Evo.Trigger(MinionEdit.MaskOffEv, _mm);
                //_mm.MapCss(md, _element, md.css);

                if (Type.HasField(Window.Self, "TestRepaintAll"))
                {
                    // Just for testing that object was recored properly - TODO: remove for production
                    string id = _mm.Id;
                    new Evo().FireWith(_mm.RePaintCanvas);
                    _mm = (MinionEdit) Minion.GetMinionById(id);
                }
                //new Await().AddDx(delegate { Evo.Trigger(MinionEdit.MaskWakeEv, _mm); }).Commit();
                new Evo().FireWith(_mm.Save);
            }
            MiUiOp.Finish();
            Finish();
        }

        private Dimension CvDim(Dimension m)
        {
            if (_mm.Data.relative)
            {
                double fh = _mm.Data.offsetParentWidth/_element.OffsetParent().GetWidth();
                double fv = _mm.Data.offsetParentHeight/_element.OffsetParent().GetHeight();
                m.width = Math.Round(m.width*fh);
                m.height = Math.Round(m.height*fv);
            }
            return m;
        }
    }
}